/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with illusion. 
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <kernel/vfs.h>
#include <apimod/apimod.h>
#include <kernel/list.h>
#include <klib/lib.h>

/*
 * Simple INI file reader
 */


static bool is_white_space(char c)
{
	if(c == ' ' || c == '\t' || c == '\n')
		return 1;
	return 0;
}

//TODO Add error checking here.
int ini_read(struct ini_config **config_ret, char *file_name)
{
	uint_t fd;
	int err = open(file_name, STATE_READ, &fd);
	if(err) {
		printf("Failed to open INI file: %s\n", file_name);
		return err;
	}
	struct sys_info info;
	get_info(fd, &info);
	char *buf = (char *)malloc(info.length);
	err = read(fd, buf, 0, info.length);
	if(err) {
		printf("Failed to read INI file: %s\n", file_name);
		return err;
	}
	uint_t i, class_i = 0, setting_i = 0;
	char in_comment = 0, in_class = 0, in_setting = 0, in_setting_value = 0;
	struct ini_config *config = (struct ini_config *)malloc(sizeof(struct ini_config));
	memory_set(config, 0, sizeof(struct ini_config));
	struct ini_class *class = null;
	struct ini_setting *setting = null;
	for(i = 0; i < info.length; i++) {
		if(buf[i] == '#')
			in_comment = 1;
		if(in_comment && buf[i] == '\n')
			in_comment = 0;
		if(!in_comment) {
			if(buf[i] == '[') {
				class = (struct ini_class *)malloc(sizeof(struct ini_class));
				memory_set(class, 0, sizeof(struct ini_class));
				in_class = 1;
			} else if(in_class && buf[i] == ']') {
				class->name[class_i] = '\0';
				list_attach(config->classes, config->classes_tail, class);
				config->class_count++;
				in_class = 0;
				class_i = 0;
			} else {
				if(in_class) {
					class->name[class_i++] = buf[i];
				} else {
					if(!in_setting && !in_setting_value && !is_white_space(buf[i])) {
						setting = (struct ini_setting *)malloc(sizeof(struct ini_setting));
						memory_set(setting, 0, sizeof(struct ini_setting));
						setting->name[setting_i++] = buf[i];
						in_setting = 1;
					} else if(in_setting) {
						if(buf[i] == '=' || buf[i] == '\n') {
							in_setting = 0;
							setting->name[setting_i] = '\0';
							setting_i = 0;
							in_setting_value = 1;
							if(buf[i] == '\n') {
								goto finalize_setting;
							}
						} else {
							setting->name[setting_i++] = buf[i];
						}
					} else if(in_setting_value) {
						if(buf[i] == '\n') {
							finalize_setting:
							in_setting_value = 0;
							setting->value[setting_i] = '\0';
							list_attach(class->settings, class->settings_tail,
									setting);
							class->setting_count++;
							setting_i = 0;
						} else {
							setting->value[setting_i++] = buf[i];
						}
					}
				}
			}
		}
	}
	close(fd);
	free(buf, info.length);
	*config_ret = config;
	return 0;
}

/*
 * Free the ini_config structure containing classes and settings
 */
int ini_dispose(struct ini_config *config)
{
	struct ini_class *class;
	for(class = config->classes; class; ) {
		struct ini_class *next_class = class->next;
		struct ini_setting *setting;
		for(setting = class->settings; setting; ) {
			struct ini_setting *next_setting = setting->next;
			free(setting, sizeof(struct ini_setting));
			setting = next_setting;
		}
		free(class, sizeof(struct ini_class));
		class = next_class;
	}
	free(config, sizeof(struct ini_config));
	return 0;
}

char *ini_get_value(struct ini_config *config, char *path)
{
	char name[INI_NAME_LEN];
	path = separate(name, path, ':');
	struct ini_class *class;
	list_for(config->classes, class) {
		if(string_equals(class->name, name)) {
			separate(name, path, ':');
			struct ini_setting *setting;
			list_for(class->settings, setting) {
				if(string_equals(setting->name, name)) {
					return setting->value;
				}
			}
		}
	}
	return null;
}

struct ini_class *ini_get_class(struct ini_config *config, char *name)
{
	struct ini_class *class;
	list_for(config->classes, class) {
		if(string_equals(class->name, name))
			return class;
	}
	return null;
}
